using System;
using System.Text;
using System.Data;
using System.Collections;
using System.Collections.Generic;

using Framework;

namespace SchemaMonitor
{
    //Table-Mapping Class (Customisable half)
    public partial class CSignature
    {
        #region Constants
        public const string JOIN_TYPE = TABLE_NAME + " INNER JOIN " + CType.TABLE_NAME + " ON TypeId=SigTypeId";
        #endregion

        #region Constructors (Public)
        //Default Connection String
        public CSignature() : base() {}

        //Alternative Connection String
        public CSignature(CDataSrc dataSrc) : base(dataSrc) {}

        //Hidden  (UI code should use cache instead)
        protected internal CSignature(Guid sigHash, int SigTypeId ) : base(sigHash, SigTypeId) {}
        protected internal CSignature(CDataSrc dataSrc, Guid sigHash, int SigTypeId ) : base(dataSrc, sigHash, SigTypeId) {}
        protected internal CSignature(CDataSrc dataSrc, Guid sigHash, int SigTypeId , IDbTransaction txOrNull) : base(dataSrc, sigHash, SigTypeId, txOrNull) { }

        //Custom
        public CSignature(Type t)
        {
            this.SetSig(t.ToString(), EDiscovery.System);
        }
        public CSignature(string metaTypeName, bool isManual=false)
        {
            this.SetSig(metaTypeName, isManual ? EDiscovery.Manual : EDiscovery.Auto);
        }
        public CSignature(json.CMetadata meta)
        {
            this.SetSig(meta.Pattern, EDiscovery.Auto);
        }
        #endregion

        #region Default Values
        protected override void InitValues_Custom()
        {
            _sigCreated = DateTime.Now;
        }
        #endregion
        
        #region Default Connection String
        protected override CDataSrc DefaultDataSrc()  { return CDataSrc.Default;  }
        #endregion

        #region Properties - Relationships
        //Relationships - Foriegn Keys (e.g parent)
        public CDiscovery Discovery { get { return CDiscovery.Cache.GetById(this.SigDiscoveryId); } }
        public CType Type { get { return CType.Cache.GetById(this.SigTypeId); } }

        //Relationships - Collections (e.g. children)
        public CTypeList Types { get { return CSignature.Cache.GetByHash(SigHash).Types; } }
        #endregion

        #region Properties - Customisation
        //Derived/ReadOnly (e.g. xml classes, presentation logic)
        public string Name { get { return SigDescription; } }
        public EDiscovery DiscoveryId
        {
            get { return (EDiscovery)SigDiscoveryId; }
            set { SigDiscoveryId = (byte)value; }
        }

        private void SetSig(string s, EDiscovery d)
        {
            this.DiscoveryId = d;
            this.SigDescription = s;
            this.SigHash = GetHash();
        }
        private Guid GetHash() { return CBinary.Sha128_(CBinary.StringToBytes(SigDescription.ToLower())); }
        #endregion

        #region Save/Delete Overrides
        //Can Override base.Save/Delete (e.g. Cascade deletes, or insert related records)
        #endregion

        #region Custom Database Queries
        //(Not normally required for cached classes, use list class for searching etc)
        //For Stored Procs can use: MakeList (matching schema), or DataSrc.ExecuteDataset (reports etc)
        //For Dynamic sql, can use: SelectSum, SelectDistinct, SelectCount, SelectWhere (inherited methods)
        internal CSignatureList SelectInScope()
        {
            return SelectWhere(CType.InScope(), JOIN_TYPE);
        }
        #endregion



        #region Caching Details
        //Cache Key
        internal static string CACHE_KEY = typeof(CSignature).ToString();    //TABLE_NAME

        //Cache data
        private static CSignatureList LoadCache()
        {
            return new CSignature().SelectInScope();
        }
        //Cache Timeout
        private static void SetCache(CSignatureList value)
        {
            if (null != value) 
                value.Sort(); 
            CCache.Set(CACHE_KEY, value);    //Optional parameter can override timeout (otherwise uses config-settings, which default to 3hrs)
        }
        //Helper Method
        private CSignature CacheGetById(CSignatureList list)
        {
            return list.GetById(this.SigHash, this.SigTypeId);
        }
        #endregion

        #region Cloning
        public CSignature Clone(CDataSrc target, IDbTransaction txOrNull) //, int parentId)
        {
            //Shallow copy: Copies the immediate record, excluding autogenerated Pks
            CSignature copy = new CSignature(this, target);

            //Deep Copy - Child Entities: Cloned children must reference their cloned parent
            //copy.SampleParentId = parentId;

            copy.Save(txOrNull);

            //Deep Copy - Parent Entities: Cloned parents also clone their child collections
            //this.Children.Clone(target, txOrNull, copy.SigHash);

            return copy;
        }
        #endregion

        #region ToXml
        protected override void ToXml_Custom(System.Xml.XmlWriter w)
        {
            //Store(w, "Example", this.Example)
        }
        #endregion


        #region Common Signatures
        public bool IsUnknown {  get { return string.IsNullOrEmpty(Name); } }
        public bool IsInt64  { get { return Is(typeof(long)); } }
        public bool IsBool   { get { return Is(typeof(bool)); } }
        public bool IsDate   { get { return Is(typeof(DateTime)); } }
        public bool IsString { get { return Is(typeof(string)); } }
        //public bool IsArray  { get { return Is(typeof(object[])); } }
        public bool IsDict   { get { return !IsSystem ; } }
        public bool IsDataSet
        {
            get
            {
                switch (SigDescription)
                {
                    case "data": return true;
                    case "data,paging": return true;
                }
                return false;
            }
        }

        public bool Is(Type t)       { return t.ToString() == Name; }
        private bool IsListOf(Type t) { return Name == AsList(t.ToString()); }
        public bool IsSystem {  get { return !SigDescription.Contains(",") && SigDescription.Contains("System."); } }
        public bool IsList { get { return Name.Contains(L1) && Name.EndsWith(L2); } }

        internal const string L1 = "list<";
        internal const string L2 = ">";
        private static string AsList(string type)
        {
            return string.Concat(L1, type, L2);
        }
        #endregion


        #region Static - GetSig
        public static CSignature GetSigSystem(Type t)
        {
            return new CSignature(t);
        }


        public static CSignature GetSigManual(string typeName)
        {
            return new CSignature(typeName, true);
        }
        public static CSignature GetSigManual(string typeName, string apiPath)
        {
            string s = string.Concat(typeName, apiPath);
            return new CSignature(s, true);
        }


        public static CSignature GetSigAuto(string tagPath, object value, bool isList = false)
        {
            //Dict
            if (value is Dictionary<string, object>)
                return GetSigAuto(tagPath, (Dictionary<string, object>)value, isList);

            //Array
            if (value is object[])
                return GetSigArray(tagPath, (object[])value);

            //tagnames
            if (value is List<string>)
                return GetSigAuto(tagPath, (List<string>)value, isList);

            //Atomic
            value = JsonToNative(value);
            return GetSigSystem(value.GetType());
        }
        public static CSignature GetSigAuto(string tagPath, Dictionary<string, object> dict, bool isList)
        {
            var names = new List<string>(dict.Keys);
            return GetSigAuto(tagPath, names, isList);
        }
        public static CSignature GetSigAuto(string tagPath, List<string> names, bool isList)
        {
            string s = CUtilities.ListToString(names);
            if (isList)
                s = AsList(s);

            if (! string.IsNullOrEmpty(tagPath))
                s = string.Concat(tagPath, "::", s);

            return new CSignature(s);
        }
        public static CSignature GetSigArray(string tagPath, object[] arr)
        {
            //Type of first one
            if (null != arr && arr.Length > 0)
                return GetSigAuto(tagPath, arr[0], true);

            //Unknown list<>
            return GetSigSystem(new object().GetType());
        }

        private static CSignature GetSigAuto(string tagPath, string s, bool isList)
        {
            if (isList)
                s = AsList(s);
            s = string.Concat(tagPath, "/", s);
            return new CSignature(s);
        }

        internal static object JsonToNative(object value)
        {
            if (value is string)
                return JsonToNative((string)value);
            return value;   //bool, dict, array
        }
        private static object JsonToNative(string s)
        {
            //As Int
            int i= 0;
            if (int.TryParse(s, out i) && i.ToString() == s)
                return i;

            //As Long
            long id = 0;
            if (long.TryParse(s, out id) && id.ToString() == s)
                return id;

            //As Date
            if (s.Length < 12 && s.Contains("/"))
            {
                var mdy = CUtilities.SplitOn(s, "/");
                if (mdy.Count == 3)
                    try
                    {
                        return new DateTime(int.Parse(mdy[2]), int.Parse(mdy[0]), int.Parse(mdy[1]));
                    }
                    catch
                    { }
            }
            if (s.EndsWith("+0000"))
                try
                {
                    return Convert.ToDateTime(s);
                }
                catch { }

            //As String
            return s;
        }
        #endregion
    }
}